#Unit Test# Unit Test Pre-study and Practice in Golang

#Unit Test# Unit Test Pre-study and Practice in Golang

If you write code, write tests. – The Way of Testivus


目录 Table of Contents


背景

单元测试的重要性无需多言,但是由于业务迭代得超快,就很难再分出时间和精力投入到质量保障中去。尽管如此,等待接口联调的过程中既漫长无比又压力山大(毕竟谁想被挂 Tapd Bug 单呢 _(:з」∠)_

在之前的 Unit Test Pre-study and Practice in Python 文章中,已经简要地介绍了测试流程和测试用例的核心理念,本文将沿用这些方法论以及结合各位大神的博客和日常工作的体验,着重讨论以下内容:

  • 技术选型Golang 作为一门静态和强类型语言,相比于 Python 的动态和弱类型特性,带来了一些新的挑战。
  • 工程实践:业务逻辑快速验证的最佳实践,暂时不涉及脚手架设计。

技术选型

单测框架

testing

官方原生库,无断言机制,编写较繁琐。

  1. 编写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    func DoSomething() error {
    // ...
    }

    func TestDoSomething(t *testing.T) {
    // Arrange
    // ...

    // Act
    err := DoSomething()

    // Assert
    if err != nil {
    t.Errorf(err.Error())
    }
    }
  2. 运行

    1
    go test -v

GoConvey

兼容官方原生库和模拟框架,有断言机制,编写较简洁。

  1. 编写

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    func DoSomething() error {
    // ...
    }

    func TestDoSomething(t *testing.T) {
    Convey("TestDoSomething", t, func() {
    Convey("Condition 1", func() {
    // Arrange
    // ...

    // Act
    err := DoSomething()

    // Assert
    So(err, ShouldBeNil)
    })
    })
    }
  2. 运行

    1
    2
    3
    4
    # Cmd
    go test -v
    # Web
    $GOPATH/bin/goconvey

模拟框架

GoMonkey

Mock 全局变量 & 函数方法

  1. 全局变量

    1
    2
    3
    4
    5
    patches := ApplyFuncVar(&funcVar, mockVar)
    defer patches.Reset()

    patches := ApplyGlobalVar(&funcVar, mockVar)
    defer patches.Reset()
  2. 函数方法

    1
    2
    3
    4
    5
    6
    patches := ApplyFunc(funcName, mockFunc)
    defer patches.Reset()

    var ptr *SomeClass
    patches := ApplyMethod(reflect.TypeOf(ptr), "methodName", mockMethod)
    defer patches.Reset()

GoMock

Mock 接口

  1. 生成

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    // repository.go
    // Repository is the interface to be mocked
    type Repository interface {
    Create(val interface{}) error
    Get(key string) (interface{}, error)
    Update(key string, val interface{}) error
    Delete(key string) error
    }

    // mockgen -source=${file}
    // mockgen ${package} ${interface1}, ... , ${interfaceN}

    // mock_repository.go
    // MockRepository is a mock of Repository interface
    type MockRepository struct {
    ctrl *gomock.Controller
    recorder *MockRepositoryMockRecorder
    }

    // MockRepositoryMockRecorder is the mock recorder for MockRepository
    type MockRepositoryMockRecorder struct {
    mock *MockRepository
    }

    // NewMockRepository creates a new mock instance
    func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
    mock := &MockRepository{ctrl: ctrl}
    mock.recorder = &MockRepositoryMockRecorder{mock}
    return mock
    }

    // EXPECT returns an object that allows the caller to indicate expected use
    func (_m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
    return _m.recorder
    }

    // Create mocks base method
    func (_m *MockRepository) Create(_param0 []byte) error {
    // ...
    }

    // Get mocks base method
    func (_m *MockRepository) Get(_param0 string) (interface{}, error) {
    // ...
    }

    // Update mocks base method
    func (_m *MockRepository) Update(_param0 string, _param1 interface{}) error {
    // ...
    }

    // Delete mocks base method
    func (_m *MockRepository) Delete(_param0 string) error {
    // ...
    }
  2. 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    mockRepo := mock_repo.NewMockRepository(ctrl)

    InOrder (
    createCall := mockRepo.EXPECT().Create(mockVal).Return(mockErr)
    getCall := mockRepo.EXPECT().Get(mockKey).Return(mockVal, mockErr).Times(7)
    updateCall := mockRepo.EXPECT().Update(mockKey, mockVal).Return(mockErr)
    deleteCall := mockRepo.EXPECT().Create(mockKey).Return(mockErr)
    )

sqlmock

Mock 数据库

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Arrange
db, mock, err := sqlmock.New()
defer db.Close()

rows := sqlmock.NewRows(colKeys).AddRow(colVals)
mock.ExpectQuery(sqlStatement).WillReturnRows(rows)

// Act
res, err := db.Query(sqlStatement)
defer res.Close()

// Assert
for res.Next() {
res.Scan(&fileds)
// ...
}

httptest

Mock 服务器

1
2
3
4
5
6
7
8
9
10
// Arrange
r := httptest.NewRequest(method, url, nil)
w := httptest.NewRecorder()

// Act
handler(w, r)

// Assert
res := w.Result()
// ...

工程实践

  • to be continued…

总结

  • 单测框架用于断言:使用 GoConvey 框架。

  • 模拟框架用于替换:对于全局变量和函数方法使用 GoMonkey 框架,对于接口使用 GoMock 框架,对于数据库使用 sqlmock 框架,对于服务器使用 httptest 框架。

参考链接

以下文章对本文亦有贡献 :)

附录

使用文档和最佳实践 :)


Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×